#include "stdafx.h"
#include "Viewer.h"
#include "..\Render\Render.h"
#include "Model.h"
#include "Scene.h"
#include "Material.h"

	/*void Model::SetShader(const std::string& shaderFilename, const int shaderFeatureSet, const int vertexFormat) {

	if(!Render::Singleton()->GetShader(shaderFilename, vertexFormat, shaderFeatureSet, &m_Shader)) {

		Render::GetDefaultShader(1, &m_Shader);
	}
	}*/

bool CharBuffer_Equals(const tCharBuffer* pBuuffer, const char* pString)
{
	if(!pString)
		return false;

	/*uint iterations = min( min( strlen(pBuuffer->Buffer), strlen(pString)) , 48);

	for(uint i = 0; i < iterations; i++)
		if(pBuuffer->Buffer[i] != pString[i])
			return false;

	return true;*/
	return (0 == strncmp(pBuuffer->Buffer, pString, 48));
}

bool CharBuffer_Copy(tCharBuffer* pDest, tCharBuffer* pSource)
{
	if(!pDest || !pSource)
		return false;

	memcpy(pDest, pSource, sizeof(tCharBuffer));
	return true;
}

/**
	Construtor
	
	@author Dacre Denny
	
*/
Model::Model()
{	
	m_deformation = false;
	m_deformationScale = 0.0f;
	m_pDeformationStreamBuffers[0] = NULL;
	m_pDeformationStreamBuffers[1] = NULL;
	
	m_instanced = false;

	D3DXMatrixIdentity(&m_World);
	ZeroMemory(&m_ID, sizeof(tCharBuffer));
}

/**
	Construtor
	
	@author Dacre Denny
	@param pID pointer to character Buffer that initializes this instances internal ID
	
*/
Model::Model(const tCharBuffer* pID)
{	
	m_deformation = false;
	m_deformationScale = 0.0f;
	m_pDeformationStreamBuffers[0] = NULL;
	m_pDeformationStreamBuffers[1] = NULL;
	
	m_instanced = false;

	D3DXMatrixIdentity(&m_World);
	memcpy(&m_ID, pID, sizeof(tCharBuffer)); 
}

/**
	Construtor
	
	@author Dacre Denny
	
*/
tPart::tPart() {

	m_TriangleCount = 0;
	m_pTriangleData = NULL;
	
	m_pShader = NULL;
	m_pVertexBuffer = NULL;
	m_pIndexBuffer = NULL;

	ZeroMemory(m_channels, sizeof(tChannel) * 3);
}

//int tPart::GetShaderAttributes() {
//
//}

void Model::ReleaseInstanceChild(tInstanceChild* pChild) {
	
	if(pChild) {

		Render::Singleton()->ReleaseResource(&pChild->m_pBuffer);
		Materials::Release(&pChild->m_pCookie);
		
		for(uint i = 0; i < 3; i++) {
			
			if(pChild->m_hChannels[i])
				Materials::Release(pChild->m_hChannels[i]);
		}

		FOREACH(tInstanceChild, i, m_instanceChildren) {

			if(CharBuffer_Equals(&i->m_childName, pChild->m_childName.Buffer)) {

				m_instanceChildren.erase(i);
				break;
			}
		}
	}
}

void Model::ReleasePart(uint partIndex) {

	if(partIndex < m_Parts.size()) {
	
		tPart* pPart = &m_Parts[partIndex];
		Render::Singleton()->ReleaseResource(&pPart->m_pIndexBuffer);
		Render::Singleton()->ReleaseResource(&pPart->m_pVertexBuffer);
		Render::Singleton()->ReleaseShader(&pPart->m_pShader);
		
		pPart->m_pIndexBuffer = NULL;
		pPart->m_pVertexBuffer = NULL;
		pPart->m_pShader = NULL;
		pPart->m_TriangleCount = 0;

		if(pPart->m_pTriangleData) {

			delete [] pPart->m_pTriangleData;
			pPart->m_pTriangleData = NULL;
		}

		for(uint i = 0; i < 3; i++) {

			tChannel* pChannel = &pPart->m_channels[i];
			for(uint j = 0; j < 5; j++)
				Materials::Release(pChannel->m_hMaterials[j]);
		}

		ZeroMemory(pPart->m_channels, sizeof(tChannel) * 3);
	}
}
	
bool Model::UpdatePartGeometry(HeaderStructs::tGeometryPartHeader* pHeader, uint vertexFormat, byte* pGeometryPartData, uint partIndex) {

	tPart* pPart = NULL;

	try {

		if(partIndex > m_Parts.size() - 1 || !pHeader || !pGeometryPartData)
			throw tException("Invalid parameters or index for part: %i", partIndex);
		
		pPart = &m_Parts[partIndex];
		Render::Singleton()->ReleaseResource(&pPart->m_pIndexBuffer);
		Render::Singleton()->ReleaseResource(&pPart->m_pVertexBuffer);
		
		/*
		Does the part's shader need to be updated? It's slow to do so this is important. Only update if the incoming
		vertex format doesn't match the current shader format. If no shader exists on the current part then the shader
		needs to be created
		*/
		bool updateShader = pPart->m_pShader ? (pPart->m_pShader->VertexFormat != vertexFormat) : true;
		pPart->m_pIndexBuffer = NULL;
		pPart->m_pVertexBuffer = NULL;
		pPart->m_TriangleCount = 0;

		if(pPart->m_pTriangleData) {

			delete [] pPart->m_pTriangleData;
			pPart->m_pTriangleData = NULL;
		}

		/*
		Determine size of geometry data
		*/
		uint vertexSize = GetVertexFormatSize(vertexFormat);
		uint BufferSize = pHeader->triangleCount * 3 * vertexSize;
		if(BufferSize == 0)
			throw tException("Invalid vertex format/Buffer size for part: %i", partIndex);

		/*
		Initalize part geometry and hardware Buffers
		*/
		pPart->m_TriangleCount = pHeader->triangleCount;
		pPart->m_pVertexBuffer = NULL;
		pPart->m_pIndexBuffer = NULL;
		pPart->m_pTriangleData = new byte [BufferSize];
		memcpy(pPart->m_pTriangleData, pGeometryPartData, BufferSize);

		if(!Render::Singleton()->CreateVertexIndicieBuffer(BufferSize, (LPVOID)pPart->m_pTriangleData, 
			vertexFormat,	&pPart->m_pVertexBuffer, &pPart->m_pIndexBuffer))
			throw tException("Failed to update geometry for part: %i", partIndex);

		/*
		Create the part's shader or update it if required
		*/
		std::string Filename = pPart->m_pShader ? pPart->m_pShader->filename : std::string("default_shader.fx");
		uint Attributes = pPart->m_pShader ? pPart->m_pShader->Attributes : 0;
		if(!Render::ShadersEqual(pPart->m_pShader, Filename, vertexFormat, Attributes)) {

			/*
			Release if exists
			*/
			if(pPart->m_pShader && !Render::Singleton()->ReleaseShader(&pPart->m_pShader))
				throw tException("Failed to release shader for part: %i", partIndex);
			
			if(!Render::Singleton()->GetShader(Filename, vertexFormat, Attributes, &pPart->m_pShader, false))
				throw tException("Failed to create shader format for part: %i", partIndex);
		}

		return true;

	}
	catch(tException exception) {

		if(pPart) {

			if(pPart->m_pTriangleData) {

				delete [] pPart->m_pTriangleData;
				pPart->m_pTriangleData = NULL;
			}
		}
		if(partIndex < m_Parts.size())
			ReleasePart(partIndex);

		return false;
	}
}

tInstanceChild* Model::GetInstanceChild(tCharBuffer* pTarget) {

	if(!pTarget)
		return NULL;

	FOREACH(tInstanceChild, i, m_instanceChildren) {

		if(CharBuffer_Equals(&i->m_childName, pTarget->Buffer))
			return (tInstanceChild*)&(*i);
	}

	return NULL;
}

bool Model::UpdateInstanceChild_Mask(HeaderStructs::tMaskHeader* pHeader, char* pData) {

	try {
		/*
		Check basic preconditions
		*/
		if(!pHeader || !pData)
			throw tException("Invalid parameter(s)");

		tInstanceChild* pChild = GetInstanceChild(&pHeader->child);
		if(!pChild)
			throw tException("Invalid child");

		if(pChild->m_pCookie) {

			Materials::Release(pChild->m_pCookie);
			pChild->m_pCookie = NULL;
		}

		char keyBuffer[128];
		sprintf(keyBuffer, "P:%sC:%sS:mask", m_ID.Buffer, pChild->m_childName.Buffer);
		if(!Materials::GetMaterial(std::string(keyBuffer), &pChild->m_pCookie))
			throw tException("Failed to update child mask");
		
		if(!Materials::UpdateMaterial(SAMPLE_TEXTURE, pData, pChild->m_pCookie))
			throw tException("Failed to update child mask data");

		return true;
	}
	catch(tException exception) {

		return false;
	}
}

bool Model::UpdateInstanceChild_UV(HeaderStructs::tInstanceChildUV* pHeader) {
	
	try {
		/*
		Check basic preconditions
		*/
		if(!pHeader)
			throw tException("Invalid parameter(s)");

		tInstanceChild* pChild = GetInstanceChild(&pHeader->child);
		if(!pChild)
			throw tException("Invalid child");
		
		pChild->m_uv = pHeader->uv;
		return true;
	}
	catch(tException exception) {

		return false;
	}
}

bool Model::UpdateInstanceChild_Sampler(HeaderStructs::tInstanceChildSampler* pHeader) {

	try {
		/*
		Check basic preconditions
		*/
		if(!pHeader)
			throw tException("Invalid parameter(s)");

		tInstanceChild* pChild = GetInstanceChild(&pHeader->child);
		if(!pChild)
			throw tException("Invalid child");
		
		uint channelIndex;
		if(CharBuffer_Equals(&pHeader->channel, "lv_mask"))
			channelIndex = 0;
		else
		if(CharBuffer_Equals(&pHeader->channel, "lv_scale"))
			channelIndex = 1;
		else 
		if(CharBuffer_Equals(&pHeader->channel, "lv_rotate"))
			channelIndex = 2;
		else 
			throw tException();
		
		if(pChild->m_hChannels[channelIndex])
			Materials::Release(pChild->m_hChannels[channelIndex]);
		
		/*char keyBuffer[128];
		sprintf(keyBuffer, "P:%sC:%sS:%i", m_ID.Buffer, pChild->m_childName.Buffer, pHeader->samplerType);*/
		if(!Materials::GetMaterial(std::string(pHeader->samplerKey.Buffer), &pChild->m_hChannels[channelIndex]))
			throw tException("Failed to create child sampler");
		
		return true;
	}
	catch(tException exception) {

		return false;
	}
}

bool Model::UpdateInstanceChild(HeaderStructs::tInstanceChildChange* pHeader) {

	try {
		/*
		Check basic preconditions
		*/
		if(!pHeader)
			throw tException("Invalid parameter");

		tInstanceChild* pChild = Model::GetInstanceChild(&pHeader->child);
		if(pHeader->remove) {

			if(pChild)
				ReleaseInstanceChild(pChild);
		}
		else {
			
			if(pChild == NULL) {

				tInstanceChild child;
				child.m_uv = 0;
				child.m_hChannels[0] =
				child.m_hChannels[1] =
				child.m_hChannels[2] = NULL;
				child.m_pCookie = NULL;
				child.m_pBuffer = NULL;
				CharBuffer_Copy(&child.m_childName, &pHeader->child);
				
				if(!Render::Singleton()->CreateVertexBuffer(0, true, &child.m_pBuffer))
					throw tException("Failed to create new instance buffer");

				m_instanceChildren.push_back(child);
			}
		}

		return true;
	}
	catch(tException exception) {

		return false;
	}
}
	
bool Model::UpdatePartSamplers(HeaderStructs::tParametersPartHeader* pHeader, uint partIndex) {

	try {

		if(partIndex > m_Parts.size() - 1 || !pHeader)
			throw tException("Invalid parameter or index for part: %i", partIndex);

		tPart* pPart = &m_Parts[partIndex];
		ZeroMemory(pPart->m_channels, sizeof(tChannel) * 3);
		
		tChannel* pChannelColor = &pPart->m_channels[0];
		tChannel* pChannelNormal = &pPart->m_channels[1];
		tChannel* pChannelSpecular = &pPart->m_channels[2];

		uint shaderFeatures = 0;

		for(uint i = 0; i < 5; i++) {
			
			//CharBuffer_Copy(&pChannelColor->m_Samplers[i], &pHeader->channelColor.samplers[i]);
			//CharBuffer_Copy(&pChannelNormal->m_Samplers[i], &pHeader->channelNormal.samplers[i]);
			//CharBuffer_Copy(&pChannelSpecular->m_Samplers[i], &pHeader->channelSpecular.samplers[i]);
			//
			/*if(!CharBuffer_Equals(&pChannelColor->m_Samplers[i], ""))
				shaderFeatures |= CREATE_CHANNEL(CHANNEL_DECAL, (1<<i), DIMENSIONS_2D);

			if(!CharBuffer_Equals(&pChannelNormal->m_Samplers[i], ""))
				shaderFeatures |= CREATE_CHANNEL(CHANNEL_NORMAL, (1<<i), DIMENSIONS_2D);

			if(!CharBuffer_Equals(&pChannelSpecular->m_Samplers[i], ""))
				shaderFeatures |= CREATE_CHANNEL(CHANNEL_SPECULAR, (1<<i), DIMENSIONS_2D);*/
		}
#pragma message("BUG: when loading a scene with multi material objects, the mesh will only posses a single shader from the first part etc");
		uint VertexFormat = pPart->m_pShader ? pPart->m_pShader->VertexFormat : 0;
		uint Attributes = (pPart->m_pShader ? pPart->m_pShader->Attributes : 0) | shaderFeatures;
		if(!Render::ShadersEqual(pPart->m_pShader, std::string(pHeader->shaderFilename.Buffer), VertexFormat, Attributes)) {

			/*
			Release if exists
			*/
			if(pPart->m_pShader && !Render::Singleton()->ReleaseShader(&pPart->m_pShader))
				throw tException("Failed to release shader for part: %i", partIndex);
			
			if(!Render::Singleton()->GetShader(std::string(pHeader->shaderFilename.Buffer), VertexFormat, 
				Attributes, &pPart->m_pShader, false))
				throw tException("Failed to create shader format for part: %i", partIndex);
		}

		return true;
	}
	catch(tException exception) {

		if(partIndex < m_Parts.size())
			ReleasePart(partIndex);

		return false;
	}
}

/**
	Enables external updating of the transformation applied to this model and any instance children that
	it may have. Updates via this function are immediatly reflected in the subsequently rendered frame
	
	@author Dacre Denny
	@param pTransform pointer to the transformation data being applied to the model instance
*/
void Model::UpdateTransform(tMatrix4 *pTransform) {

	memcpy(&m_World, pTransform, sizeof(D3DXMATRIX));
}

/**
	Enables external updating of the deformation data this is applied to this model instance. 
	Updates via this function are immediatly reflected in the subsequently rendered frame
	
	@author Dacre Denny
	@param pTransform pointer to the transformation data being applied to the model instance
*/
//void Model::UpdateDeformation(HeaderStructs::tDeformationHeader *pHeader) {
//
//	if(pHeader) {
//
//		m_deformation = pHeader->remove;
//		m_deformationScale = pHeader->scale;
//	}
//
//	if(m_deformation) {
//
//		Render::Singleton()->CreateVertexBuffer(Render::STREAMOUT_MAX, true, &m_pDeformationStreamBuffers[0]);
//		Render::Singleton()->CreateVertexBuffer(Render::STREAMOUT_MAX, true, &m_pDeformationStreamBuffers[1]);
//
//		if(!m_pDeformationStreamBuffers[0] || !m_pDeformationStreamBuffers[1]) {
//
//			m_deformation = false;
//			m_deformationScale = 0.0f;
//		}
//	}
//	else {
//
//		Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[0]);
//		Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[1]);
//
//		m_pDeformationStreamBuffers[0] = NULL;
//		m_pDeformationStreamBuffers[1] = NULL;
//	}
//}

void Model::UpdateShader(HeaderStructs::tShaderHeader* pHeader, char* pData) {

	FILE* pFile = NULL;

	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader || m_Parts.size() == 0 || !pData)
			throw tException();
		
		if(pHeader->part > m_Parts.size() - 1)
			throw tException("Attemtping to modifiy channel of part that doesnt exist");

		tPart* pPart = &m_Parts[pHeader->part];
		Render::tShader* pShader = pPart->m_pShader;
		if(!pShader)
			throw tException("Bad part state. No shader found");

		std::string filename = CViewer::GetFilename(std::string(pHeader->filename.Buffer));
		std::string filepath = std::string("shaders\\") + filename;
		pFile = fopen(filepath.c_str(), "wb");
		if(!pFile)
			throw tException("Failed to open file");
		
		fwrite(pData, pHeader->filesize, 1, pFile);
		fclose(pFile);

		Render::tShader* pNewShader = NULL;
		if(!Render::Singleton()->GetShader(filename, pShader->VertexFormat, pShader->Attributes, &pNewShader))
			throw tException("Failed to create new shader");
		
		Render::Singleton()->ReleaseShader(&pShader);		
		pPart->m_pShader = pNewShader;
			
	}catch(tException e) {

		if(pFile)
			fclose(pFile);

		std::string message;
		tException::GetMessage(&e, message);
	}

}

/**
	Updates the material channel of a model part
	Changes will be immediatly reflected in the rendered result in the subsequently rendered frame
	
	@author Dacre Denny
	@param pHeader pointer to header data containing channel update data
*/
void Model::UpdateChannel(HeaderStructs::tChannelHeader* pHeader) {

	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader || m_Parts.size() == 0)
			throw tException();
		
		if(pHeader->part > m_Parts.size() - 1)
			throw tException("Attemtping to modifiy channel of part that doesnt exist");

		tPart* pPart = &m_Parts[pHeader->part];		
		if(pHeader->channel > 3)
			throw tException("Invalid channel");
		
		/*
		Clear out previous data seeing as channel is being reinintalized
		*/
		tChannel* pChannel = &pPart->m_channels[pHeader->channel - 1];
		/*if(pChannel->active) {

			for(uint i = 0; i < 5; i++) {
				if(pChannel->m_hMaterials[i])
					Materials::Release(pChannel->m_hMaterials[i]);
			}

			ZeroMemory(pChannel->m_hMaterials, sizeof(pChannel->m_hMaterials));
		}*/
	
		bool wantActive = !pHeader->remove;
		if(pChannel->active && !wantActive) {

			uint removeChannel = ~CREATE_CHANNEL((pHeader->channel - 1) * CHANNEL_BITWIDTH, 0xfffffff, DIMENSIONS_2D);

			FOREACH(tPart, i, m_Parts) {

				//Attributes |= CREATE_CHANNEL((pHeader->channel - 1) * CHANNEL_BITWIDTH, pHeader->type, DIMENSIONS_2D);
			
				tPart* pPart = &(*i);
				pPart->m_pShader->Attributes &= removeChannel;
				Render::Singleton()->GetShader(pPart->m_pShader->filename, pPart->m_pShader->VertexFormat, 
					pPart->m_pShader->Attributes, &pPart->m_pShader, true);

			}
			pChannel->active = false;
		}
		else if(!pChannel->active && wantActive) {

			pChannel->active = true;
		}
			
	}catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
	}
}

int GetSamplerTypeIndex(uint type) {
	
	uint samplerIndex = 0;
	switch(type) {
		
		default:
			return -1;
		case SAMPLE_CHECKER:
			samplerIndex++;
		case SAMPLE_NOISE:
			samplerIndex++;
		case SAMPLE_RAMP:
			samplerIndex++;
		case SAMPLE_GRID:
			samplerIndex++;
		case SAMPLE_TEXTURE:
			samplerIndex;
	}

	return samplerIndex;
}

/**
	Updates the contents of a sampler within the targeted part and channel of this model instance.
	If the model is not in the correct 'state', no changes are applied and the
	Changes will be immediatly reflected in the rendered result in the subsequently rendered frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the material to direct the datas application
	any sampler that previosuly existed respecitive of the header contents, is cleared
*/
void Model::UpdateSampler(HeaderStructs::tSamplerHeader* pHeader) {

	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader || m_Parts.size() == 0)
			throw tException();
		
		if(pHeader->part > m_Parts.size() - 1 || pHeader->channel > 3)
			throw tException("Attemtping to modifiy sampler of part/channel that doesnt exist");

		tPart* pPart = &m_Parts[pHeader->part];

		tChannel* pChannel = &pPart->m_channels[pHeader->channel - 1];
		if(!pChannel->active) 
			throw tException("Target channel inactive. Cannot modify the channel's samplers");

		int index = GetSamplerTypeIndex(pHeader->type);
		if(index < 0)
			throw tException();
		
		Render::tShader* pShader = pPart->m_pShader;
		if(!pShader)
			throw tException("Target part has no shader. Cannot modify samplers");

		uint Attributes = pShader->Attributes;
		if(pHeader->remove) {

			Attributes &= ~CREATE_CHANNEL((pHeader->channel - 1) * CHANNEL_BITWIDTH, pHeader->type, DIMENSIONS_2D);
			if(pChannel->m_hMaterials[index]) {

				Materials::Release(pChannel->m_hMaterials[index]);
				pChannel->m_hMaterials[index] = NULL;
			}
		}
		else {

			Attributes |= CREATE_CHANNEL((pHeader->channel - 1) * CHANNEL_BITWIDTH, pHeader->type, DIMENSIONS_2D);
			if(!Materials::GetMaterial(std::string(pHeader->sampler.Buffer), &pChannel->m_hMaterials[index]))
				throw tException("Failed to get sampler");
			
			/*Materials::UpdateMaterial(pHeader->type, pData, pChannel->m_hMaterials[index]);*/
		}
		
		if(Attributes != pShader->Attributes) {
			
			if(!Render::Singleton()->GetShader(pShader->filename, pShader->VertexFormat, Attributes, &pPart->m_pShader, false))
				throw tException("Failed to create shader format for part: %i", pHeader->part);

			/*
			Release if exists
			*/
			if(!Render::Singleton()->ReleaseShader(&pShader))
				throw tException("Failed to release shader for part: %i", pHeader->part);
		}
	
	}catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
	}
}
/**
	Enables external updating of geometry that represents this mesh instance. The updates should be reflected
	immediatly in the subsequent rendering frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the geoemtry data
	@param pGeometryData pointer to the geometry data being applied during the update
	
*/
void Model::UpdateGeometry(HeaderStructs::tGeometryHeader *pHeader, byte* pGeometryData) {
	
	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader || !pGeometryData)
			throw tException();

		/*
		Check header integrity
		*/
		uint partCount = pHeader->partCount;
		uint vertexSize = GetVertexFormatSize(pHeader->vertexFormat);
		if(partCount == 0 || vertexSize == 0 || !CharBuffer_Equals(&pHeader->target, m_ID.Buffer))
			throw tException("Invalid header");
	
		/*
		Update model part space if nessisary
		*/
		byte* pReadHeader = pGeometryData;
		if(partCount != m_Parts.size()) {
			
			if(partCount < m_Parts.size()) {

				for(uint i = (partCount > 0) ? partCount : 0; i < m_Parts.size(); i++)
					ReleasePart(i);
			}
			m_Parts.resize(partCount);
		}

		/*
		Update model parts
		*/
		for(uint i = 0; i < partCount; i++) {
			
			HeaderStructs::tGeometryPartHeader* pPartHeader = (HeaderStructs::tGeometryPartHeader*)pReadHeader;
			pReadHeader += sizeof(HeaderStructs::tGeometryPartHeader);

			if(!UpdatePartGeometry(pPartHeader, pHeader->vertexFormat, pReadHeader, i))
				throw tException("Failed to update part: %i", i);

			pReadHeader += (vertexSize * pPartHeader->triangleCount * 3);
		}
		
		/*
		Optional 
		*/
		Scene::WriteToDisk(this, std::string(m_ID.Buffer));
	}
	catch(tException e) {

		for(uint i = 0; i < m_Parts.size(); i++)
			ReleasePart(i);

		std::string message;
		tException::GetMessage(&e, message);
	}
}

/**
	Enables external updating of parameters used to specifiy this model instance. The updates should be reflected
	immediatly in the subsequent rendering frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the parameter data
	
*/
void Model::UpdateParameters(HeaderStructs::tParametersHeader *pHeader) {
	
	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader)
			throw tException();

		if(m_deformation ^ pHeader->deformation) {
			
			if(pHeader->deformation) {
			
				Render::Singleton()->CreateVertexBuffer(Render::STREAMOUT_MAX, true, &m_pDeformationStreamBuffers[0]);
				Render::Singleton()->CreateVertexBuffer(Render::STREAMOUT_MAX, true, &m_pDeformationStreamBuffers[1]);
			}
			else {

				Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[0]);
				Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[1]);
			}
			
			FOREACH(tPart, i, m_Parts) {

				Render::tShader* pCurrent = i->m_pShader;
				
				if(pCurrent->VertexFormat & (VERTEX_UV0 | VERTEX_NORMAL)) {
					if(!Render::Singleton()->GetShader(pCurrent->filename,
						pHeader->deformation ? (pCurrent->VertexFormat | VERTEX_DEFORMATION) : (pCurrent->VertexFormat & ~VERTEX_DEFORMATION),
						pCurrent->Attributes, &i->m_pShader))
							throw tException("Failed to get defomation shader for part");
				}
			}
		}

		m_instanced = pHeader->instancable;
		m_deformation = pHeader->deformation;
		m_deformationScale = pHeader->deformationScale;

		
		/*
		Update instanced state
		*/
		if(m_instanced) {
			
			FOREACH(tInstanceChild, i, m_instanceChildren)
				ReleaseInstanceChild(&(*i));
			m_instanceChildren.clear();
		}

	}
	catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
	}
}

/**
	Enables external updating of instancing data to enable the application to deliver instanced geometry across
	this geometry instance. The updates should be reflected	immediatly in the subsequent rendering frame
	
	@author Dacre Denny
	@param pHeader pointer to header of the instance data
	@param pInstancingData pointer to the instance data being applied during the update
	
*/
void Model::UpdateInstancing(HeaderStructs::tInstanceHeader* pHeader, byte* pInstancingData) {
	
	try {

		/*
		Check basic preconditions
		*/
		if(!pHeader || !pInstancingData)
			throw tException();

		/*
		Check header integrity
		*/
		uint instanceCount = pHeader->instanceCount;
		if(!CharBuffer_Equals(&pHeader->target, m_ID.Buffer))
			throw tException("Invalid header");
		
		/*
		Determine a 'safe' range to iterate in terms of the model parts
		*/
		/*if(instanceCount != m_instanceChildren.size()) {

			if(instanceCount < m_instanceChildren.size() - 1) {
				for(uint i = instanceCount - 1; i < m_instanceChildren.size(); i++)
					ReleaseInstanceChild(i);
			}
			
			m_instanceChildren.resize(instanceCount);
		}*/
		
		/*
		Begin reading instancing data
		*/
		byte* pReadHeader = pInstancingData;

		/*
		Update instance children
		*/
		for(uint i = 0; i < instanceCount; i++) {
			
			HeaderStructs::tInstanceChildHeader* pChild = (HeaderStructs::tInstanceChildHeader*)pReadHeader;
			pReadHeader += sizeof(HeaderStructs::tInstanceChildHeader);

			//if(!UpdateInstanceChild(pChild))
			//	throw tException("Failed to update child instance: %i", i);
		}

	}
	catch(tException e) {

		std::string message;
		tException::GetMessage(&e, message);
	}
}

/**
	Attempts to retrieve this model instance's ID.

	@author Dacre Denny
	@param pOut the address of memory to store the instances ID
	@return true if the function is successful otherwise false
*/
bool Model::GetID(tCharBuffer* pOut) {

	if(!pOut)
		return false;

	return CharBuffer_Copy(pOut, &m_ID);
}

/**
	Attempts to retrieve an instance child model which is matches the specified name and that is 
	owned by this model instance as a child of itself. The function will only return true if a
	suitable match is found.

	@author Dacre Denny
	@param pName the child geometry name to look for
	@param pOut address of pointer to store the model if it is found

	@return true if the function is successful otherwise false
*/
bool Model::GetInstanceChild(tCharBuffer* pName, Model** pOut) {
	
	if(!pName || !pOut)
		return false;

	FOREACH(tInstanceChild, i, m_instanceChildren) {

		Model* pInstanceChild = NULL;
		if(Scene::GetGeometry(&pInstanceChild, &i->m_childName))
			*pOut = pInstanceChild;
	}

	*pOut = NULL;
	return false;
}

/**
	Attempts to retrieve an instance child model which is matches the specified name and that is 
	owned by this model instance as a child of itself. The function will only return true if a
	suitable match is found.

	@author Dacre Denny
	@param pOut the address of memory to store the transformation
*/
bool Model::GetTransform(tMatrix4 *pOut) {

	if(!pOut)
		return false;

	memcpy(pOut, &m_World, sizeof(float) * 16);
	return true;
}

/**
	Attempts to retrieve deformation data from this model instance

	@author Dacre Denny
	@param pOut the address of memory to store the deformation data
*/
bool Model::GetInfoDeformation(QuerysStructs::tDeformationHeader *pOut) {

	if(!pOut)
		return false;

	pOut->isDeformation = m_deformation;
	pOut->scale = m_deformationScale;
	return true;
}

/**
	Attempts to retrieve instancing data from this model instance. Ownership of allocated 
	memory is released to client

	@author Dacre Denny
	@param pOut the address of memory to store the instacing header data
	@param pChildrenOut the address of memory to store the instacing child data
*/
bool Model::GetInfoInstancing(QuerysStructs::tInstancingHeader* pOut, QuerysStructs::tInstanceChildHeader* pChildrenOut) {

	try {

		if(!pOut || !pChildrenOut)
			throw tException("Invalid parameters");
		
		uint childCount = m_instanceChildren.size();
		pOut->childCount = childCount;
		pOut->isInstanced = m_instanced;

		if(childCount == 0) {

			if(pOut) pOut = NULL;
			if(pChildrenOut) pChildrenOut = NULL;
		}
		else {

		/*	if(pChildrenOut) {

				pChildrenOut = new QuerysStructs::tInstanceChildHeader [childCount];

				uint n = 0;
				FOREACH(tInstanceChild, i, m_instanceChildren) {

					QuerysStructs::tInstanceChildHeader* pChild = &pChildrenOut[n];

					CharBuffer_Copy(&pChild->filenameCookie, &i->m_cookie);
					CharBuffer_Copy(&pChild->samplerPosition, &i->m_channels[0]);
					CharBuffer_Copy(&pChild->samplerScale, &i->m_channels[1]);
					CharBuffer_Copy(&pChild->samplerRotation, &i->m_channels[2]);
					CharBuffer_Copy(&pChild->name, &i->m_childName);

					n++;
				}
			}*/
		}

	}catch(tException exception) {

		if(pOut)
			memset(pOut, 0, sizeof(QuerysStructs::tGeometryHeader));

		if(pChildrenOut) {
		
			delete [] pChildrenOut;
			pChildrenOut = NULL;
		}
	}
}
	
/**
	Attempts to retrieve geometry data from this model instance. Ownership of allocated 
	memory is released to client

	@author Dacre Denny
	@param pOut the address of memory to store the deformation data
*/
bool Model::GetInfoGeometry(QuerysStructs::tGeometryHeader *pOut, QuerysStructs::tGeometryPartHeader **pOutParts, byte ***pOutTriangles) {

	QuerysStructs::tGeometryPartHeader* pLocalOutParts = NULL;
	byte** pLocalOutTriangles = NULL;
	uint partCount = 0;

	try {

		if(!pOut || (!pOutParts && !pOutTriangles))
			throw tException("Invalid parameters");
		
		partCount = m_Parts.size();
		pOut->partCount = partCount;
		pOut->vertexFormat = 0;

		if(partCount == 0) {

			if(pOut) memset(pOut, 0, sizeof(QuerysStructs::tGeometryHeader));
			if(pOutParts) pOutParts = NULL;
			if(pOutTriangles) pOutTriangles = NULL;
		}
		else {

			if(pOutParts) {
			
				pLocalOutParts = new QuerysStructs::tGeometryPartHeader [partCount];
				*pOutParts = pLocalOutParts;
			}

			if(pOutTriangles) {

				pLocalOutTriangles = new byte* [partCount];
				*pOutTriangles = pLocalOutTriangles;
			}

			uint n = 0;
			FOREACH(tPart, i, m_Parts) {

				uint partVertexFormat = i->m_pShader->VertexFormat;

				if(pOut->vertexFormat == 0)
					pOut->vertexFormat = partVertexFormat;
				else if(pOut->vertexFormat != partVertexFormat)
					throw tException("Invalid model state");

				if(pOutParts)  {

					pLocalOutParts[n].triangeCount = i->m_TriangleCount;
					memcpy(pLocalOutParts[n].channel, i->m_channels, sizeof(tChannel) * 3);				
					memcpy(&pLocalOutParts[n].shader, i->m_pShader->filename.c_str(), min(sizeof(tCharBuffer), i->m_pShader->filename.length()));
				}

				if(pOutTriangles) {

					uint BufferSize = GetVertexFormatSize(partVertexFormat) * 3 * i->m_TriangleCount;
					pLocalOutTriangles[n] = new byte [BufferSize];
					memcpy(pLocalOutTriangles[n], i->m_pTriangleData, BufferSize);
				}
				n++;
			}

		}

		return true;

	}catch(tException exception) {

		if(pOut)
			memset(pOut, 0, sizeof(QuerysStructs::tGeometryHeader));

		if(pLocalOutParts) {
		
			delete [] pLocalOutParts;
			*pOutParts = NULL;
		}

		if(pLocalOutTriangles) {

			for(uint i = 0; i < partCount; i++) {
			
				if(pLocalOutTriangles[i])
					delete [] pLocalOutTriangles[i];
			}

			delete [] pLocalOutTriangles;
			*pOutTriangles = NULL;
		}
	}
}

void Model::DrawInstanceChildren() {

	FOREACH(tInstanceChild, i, m_instanceChildren) {

		Model* pInstanceBufferChild = NULL;
		if(!Scene::GetGeometry(&pInstanceBufferChild, &i->m_childName))
			continue;

		if(pInstanceBufferChild->m_instanced) {

			FOREACH(tPart, j, m_Parts) {

				uint instances;
				Render::tResource* pCookieTexture = NULL;
				if(!i->m_pCookie ^ Materials::GetMaterialTexture(i->m_pCookie, &pCookieTexture)) {
					
					Render::Singleton()->StreamOutToBuffer(&(*j), pCookieTexture,i->m_uv, &m_World, &(*i), &instances);
					pInstanceBufferChild->Render(i->m_pBuffer, instances);
				}
			}
		}
	}
}

void Model::Render(Render::tResource* pInstanceBuffer, UINT instanceCount) {

	/*
	Render the geoemtry under normal circumstances
	*/
	FOREACH(tPart, i, m_Parts) {

		tPart* pPart = &(*i);
		if(!pPart)
			continue;

		uint* pInstanceBufferCount = NULL;
		bool isInstanced = false;
		if(pInstanceBuffer && instanceCount > 0) {

			isInstanced = true;
			pInstanceBufferCount = &instanceCount;
		}

		Render::tShader* pShader = pPart->m_pShader;
		if(!pShader)
			continue;

		uint shaderPasses = 0;
		if(!Render::Singleton()->BeginShader(pShader, isInstanced, &shaderPasses))
			continue;
	
        Render::Singleton()->SetShaderMatrix( "World", &m_World);
		for(uint j = 0; j < 5; j++) {
			
			if(pPart->m_channels[0].m_hMaterials[j])
				Materials::ApplyToShader(pShader, "lv_color", pPart->m_channels[0].m_hMaterials[j]);
			if(pPart->m_channels[1].m_hMaterials[j])
				Materials::ApplyToShader(pShader, "lv_normal", pPart->m_channels[1].m_hMaterials[j]);
			if(pPart->m_channels[2].m_hMaterials[j])
				Materials::ApplyToShader(pShader, "lv_aux", pPart->m_channels[2].m_hMaterials[j]);
		}
		if(m_deformation)
			Render::Singleton()->SetShaderFloat( "DeformationScale", m_deformationScale);

		Render::tResource** pBindArray = NULL;
		uint* pStrideArray = NULL;
		uint bindCount = 0;

		if(isInstanced) {

			pBindArray = new Render::tResource* [2];
			pBindArray[0] = NULL;
			pBindArray[1] = pInstanceBuffer;

			pStrideArray = new uint [2];
			pStrideArray[0] = GetVertexFormatSize(pShader->VertexFormat);
			pStrideArray[1] = Render::INSTANCE_STRIDE;

			bindCount = 2;
		}
		else {

			pBindArray = new Render::tResource* [1];
			pBindArray[0] = NULL;

			pStrideArray = new uint [1];
			pStrideArray[0] = GetVertexFormatSize(pShader->VertexFormat);

			bindCount = 1;
		}


		for(uint j = 0; j < shaderPasses; j++) {

			if(!Render::Singleton()->BeginShaderPass(j))
				continue;

			Render::tResource* pVertexBuffer = pPart->m_pVertexBuffer;
			Render::tResource* pIndexBuffer = pPart->m_pIndexBuffer;
			

			if(m_deformation) {

				bool even = ((j % 2) == 0);
				pBindArray[0] = (j == 0) ? pVertexBuffer : m_pDeformationStreamBuffers[ even ? 1 : 0];
				Render::tResource* pOutTessilation = m_pDeformationStreamBuffers[ even ? 0 : 1];
				Render::Singleton()->BindStreamOutBuffer(pOutTessilation, pStrideArray[0]);
			}
			else {

				pBindArray[0] = pVertexBuffer;
			}
			
			if(!Render::Singleton()->BindVertexBuffer(pBindArray, pStrideArray, bindCount))
				continue;

			if(pIndexBuffer) {
			
				if(!Render::Singleton()->BindIndexBuffer(pIndexBuffer, sizeof(uint)))
					continue;

				Render::Singleton()->DrawIndexedTriangleList(pPart->m_TriangleCount, pInstanceBufferCount, m_deformation ? &j : NULL);
			}
			else {

				Render::Singleton()->DrawTriangleList(pPart->m_TriangleCount, pInstanceBufferCount);
			}
			
			Render::Singleton()->EndShaderPass();

		}
		
		delete [] pBindArray;
		delete [] pStrideArray;

		Render::Singleton()->EndShader();

	}
}
	
/**
	Calling this function renders the specified mesh. Optional parameters for instancing this
	mesh according to a specified instance Buffer is also possible.
	
	@author Dacre Denny
	@param pShader the shader to override this geometry with, otherwise use it's own
	@param pInstanceBufferChild the child geometry to be rendered across the mesh
	@param instanceCount the number of instance children to be rendered
*/
void Model::Draw() { //Render::tShader* pShader, bool deformationOverride, Render::tResource* pInstanceBufferBuffer, UINT instanceCount) {

	/*
	If this object is an instance child then we don't want to draw it as a normal object in the scene.
	It's rendering is instead restricted to instancing via an instance Buffer
	*/
	if(m_instanced)
		return;

	Render(NULL, 0);
	/*
	Render any instance children that are assigned to this model instance
	*/
	DrawInstanceChildren();
}

void Model::Release() {

	
	FOREACH(tInstanceChild, i, m_instanceChildren)
		ReleaseInstanceChild(&(*i));
	m_instanceChildren.clear();

	for(uint i = 0; i < m_Parts.size(); i++)
		ReleasePart(i);
	m_Parts.clear();

	Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[0]);
	Render::Singleton()->ReleaseResource(&m_pDeformationStreamBuffers[1]);

	m_deformationScale = 0.0f;
	m_deformation = false;

	m_instanced = false;

	D3DXMatrixIdentity(&m_World);
}